﻿# coding=UTF-8
import os
import re
import argparse
import shutil
import subprocess
import datetime
import pprint
import tempfile
import sys
import signal
import csv

def signal_handler(signal, frame):
	log(os.linesep+os.linesep+"[!]You pressed Ctrl+C!")
	cleanExit(0)
signal.signal(signal.SIGINT, signal_handler)

def nowtag():
	return datetime.datetime.now().strftime("%Y-%m-%d_%H%M%S")

def verbose(obj):
	if args.verbose:
		print('[v] ',pprint.pprint(obj))
		
def debug(obj):
	if args.debug:
		print('[d] ',pprint.pprint(obj))

def get_drives(nonetdrives=False):
	drives = []
	if sys.platform.startswith('win'):
		dl = 'CDEFGHIJKLMNOPQRSTUVWXYZ'
		# drives = ['%s:' % d for d in dl if not (os.system("vol %s: 2>nul>nul" % (d)) and (isShared('%s:' % d) and nonetdrives))]
		# drives = ['%s:' % d for d in dl if (not os.system("vol %s: 2>nul>nul" % (d)))]
		# drives = ['%s:' % d for d in dl if (not isShared('%s:' % d) ^ nonetdrives)]
		for d in dl:
			if not os.system("vol %s: 2>nul>nul" % (d)):
				if nonetdrives:
					if isShared('%s:' % d):
						drives.append('%s:' % d)
				else:
					drives.append('%s:' % d)
	elif sys.platform.startswith('linux'):
		drives.append('/')
	else:
		log("[!] Error : unknown system")
	return drives
	
def isShared(drive):
	with open(os.devnull) as fnull:
		ret = subprocess.call(['net', 'use', drive], stdout=fnull, stderr=fnull)
	return ret
	
def log(obj):
	print(obj)
	# save obj to file
	if args.output:
		logfile.write(obj+os.linesep)
		logfile.flush()

def sysCmd(cmd):
	ret = cmd
	if sys.platform.startswith('win'):
		pass
	elif sys.platform.startswith('linux'):
		if isinstance(ret, str):
			ret = 'wine '+cmd
		else:
			ret.insert(0,'wine')
	else:
		log("[!] Error : unknown system")
	return ret
		
def analyseArchive(fn):
	found = False
	if args.fast:
		verbose(os.linesep+"Skipping archive %s" % fn)
		return found
	verbose(os.linesep+"Analysing archive %s" % fn)
	a, fileName = os.path.split(fn)
	temp_folder = tempfile.mkdtemp(prefix=fileName+'_',dir=supertemp_folder) #path manipulation
	# print('temp folder='+temp_folder+' for archive '+fn+"\n")
	debug('temp folder='+temp_folder+' for archive '+fn)#path manipulation fn=filename
	verbose('Extracting '+fn+' ...')
	zcmd = sysCmd(['7za.exe', 'x', '-y', '-o'+temp_folder, fn, '-ppassword'] )
	with open(os.devnull) as fnull:
		try:
			subprocess.check_call(zcmd, stdout=fnull) #should list & do better but that breaks the recurence too bad, todo in next vers
			found = analyseFolder(temp_folder)
		except Exception as e:
			try:
				verbose("error on extracting %s : %s" % (fn,e))
			except:
				verbose("unknown error on extracting %s" % fn)
			
	shutil.rmtree(temp_folder)
	if found:
		log('End of archive analysis %s' % fn)
	return found
	
def analyseFile(fn):
	found = False
	try:
		fileName, fileExtension = os.path.splitext(fn) #path manipulation
		a, fileName = os.path.split(fn)
		fileExtension = fileExtension[1:]
		if os.path.normcase(os.path.abspath(fn))==os.path.normcase(os.path.abspath(args.output)):
			verbose( "Skiping logfile %s" %fn)
			return found
		# print(os.path.normcase(os.path.abspath(fn)))
		if blacklist.search(os.path.normcase(os.path.abspath(fn))):
			verbose( "Skiping blacklisted %s" %fn)
			return found
		if fileExtension in exts:
			size = os.stat(fn).st_size
			if size <= args.maxsize*1024*1024:
				found = analyseContent(fn)
		elif fileExtension in archivesext:
			size = os.stat(fn).st_size
			if size <= args.maxsize*1024*1024:
				found = analyseArchive(fn)
		elif regex.search(fileName):
			size = os.stat(fn).st_size
			log( os.linesep+"[+]Found file %s (size %d)" % (fn, size))
			if size <= args.maxsize*1024*1024:
				copyFile(fn)
			found = True
	except Exception as e:
		try :
			print("[!]Error on analysing file '%s' :"+os.linesep+"\t %s" % (fn,e.strerror))
		except:
			print("[!]Unknown Error on analysing file '%s'" % fn)
	return found

def analyseContent(fn):
	found = False
	fileName, fileExtension = os.path.splitext(fn)
	fileExtension = fileExtension[1:]
	alreadyFound = False
	if exts[fileExtension] != '':
		# If fast mode, don't do specials
		if args.fast:
			return found			
		# data=os.system(exts[fileExtension].replace('%1' ,fn))
		run = sysCmd(exts[fileExtension].replace('%1' ,'"%s"' % fn))
		verbose(run)
		data=subprocess.Popen(run,stdout=subprocess.PIPE, shell=True).communicate()[0].split(os.linesep)
	else:
		with open(fn, 'r') as f: data = f.readlines()
	for i, line in enumerate(data):
		if len(line) <= 512: #Sanity check
			if regex.search(line):
				if not alreadyFound:
					log( "[+]Found in %s:" % fn)
					alreadyFound = True
					found = True
					copyFile(fn)
				log("\t%s" % '\t'.join(data[i-args.context:i+args.context+1]).strip())
	return found

def copyFile(fn):
	if args.savefiles:#path manipulation
		log("\tCopying file to %s" % args.savefiles)
		head, tail = os.path.splitext(os.path.basename(fn))
		dst_file = os.path.join(args.savefiles, os.path.basename(fn))
		count = 0
		while os.path.exists(dst_file):
			count += 1
			dst_file = os.path.join(args.savefiles, '%s-%d%s' % (head, count, tail))#path manipulation
		shutil.copy2(fn, dst_file)
		
def analyseFolder(folder):
	found = False
	verbose( "Searching in %s" % os.path.normcase(os.path.abspath(folder)))
	debug("savefile= %s" % os.path.normcase(os.path.abspath(args.savefiles)))
	for r,d,f in os.walk("%s" % folder, topdown=True):
		depth = r[len(folder)+len(os.path.sep):].count(os.path.sep) 
		if args.maxdepth != -1 and depth >= args.maxdepth:
			# Don't dig deeper
			d[:]=[]
		for files in f:
			if args.savefiles:
				nok = [os.path.normcase(os.path.abspath(args.savefiles)), os.path.normcase(os.path.abspath(supertemp_folder))]
				if os.path.normcase(os.path.abspath(r)) in nok:
					verbose( "Skiping savefile folder %s" %r)
					continue
			fn = os.path.join(r,files)#path manipulation
			debug(fn)
			found = analyseFile(fn) or found
	return found
	
def main():
	
	if args.root.lower() == 'all':
		drives = get_drives(nonetdrives=args.nonetdrives)
	else:
		drives = ['%s' % args.root]
	log('Passgrep launched on %s' % (', '.join(drives)))
		
	for drive in drives:
		analyseFolder(drive)

	if args.output:
		logfile.close()

def AnalyseAutorun():	
	found = False
	if not sys.platform.startswith('win'):
		return found
	log('Passgrep scanning autorun entries')
	cmd = 'autorunsc.exe -accepteula -a -c'
	with open(os.devnull) as fnull:
		ret = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=fnull, shell=True).communicate()[0].split(os.linesep)
	verbose(ret)
	csvreader = csv.reader(ret, delimiter=',', quotechar='"')
	for row in csvreader:
		if len(row) >= 6 :
				found = analyseFile(row[6])
			# if len(row) >= 7 : print(row[7])
	return found
		
def cleanExit(code):
	print('Deleting temp files')
	shutil.rmtree(supertemp_folder)
	sys.exit(code)
	
def confirm(prompt_str="Confirm", allow_empty=True, default=True):
	fmt = (prompt_str, 'Y', 'n') if default else (prompt_str, 'N', 'y')
	if allow_empty:
		prompt = '%s [%s]|%s: ' % fmt
	else:
		prompt = '%s %s|%s: ' % fmt
 
	if args.yestoall:
		print(prompt+'y')
		return default

	while True:
		ans = raw_input(prompt).lower()

		if ans == '' and allow_empty:
			return default
		elif ans == 'y':
			return True
		elif ans == 'n':
			return False
		else:
			print('Please enter y or n.'	)
	
if __name__ == "__main__":
	parser = argparse.ArgumentParser(description='Grep drives / folders for passwords.')
	parser.add_argument('-r', '--root', help='[Mandatory] Root emplacement of search. \'all\' for all drives', default='')
	parser.add_argument('-n', '--nonetdrives', help='Do not scan in net drives (with -r all)', action='store_true')
	parser.add_argument('-a', '--autorun', help='Scan autorun entries', action='store_true')
	parser.add_argument('-s', '--savefiles', help='Saves the matched files to the specified folder', default='')
	parser.add_argument('-o', '--output', help='Saves output to file', default='passgrep_'+nowtag()+'.log')
	parser.add_argument('-v', '--verbose', help='Be verbose', action='store_true')
	parser.add_argument('-m', '--maxsize', help='Max size of file for analysing and copying(in MiB)', default=20, type=int)
	parser.add_argument('-d', '--maxdepth', help='Max depth of search (-1 for unlimited)', default=-1, type=int)
	parser.add_argument('-C', '--context', help=u'àla Grep context (lines above / below)', default=0, type=int)
	parser.add_argument('-y', '--yestoall', help='Answer Yes to all questions', action='store_true')
	parser.add_argument('-f', '--fast', help='Fast mode: no archives, no specials, just plain grep', action='store_true')
	parser.add_argument('--debug', help='Show debug info', action='store_true')
	args = parser.parse_args()
	
	if len(sys.argv)==1:
		parser.print_help()
		sys.exit(1)

	if args.savefiles:
	# args.savefiles = os.path.abspath(args.savefiles)	
		if not os.path.exists(args.savefiles):
			os.makedirs(args.savefiles)#path manipulation

	if args.output:#path manipulation
		global logfile
		f = args.output
		m = 'w'
		if os.path.isfile(f):
			if confirm("Logfile already exists, append ? (or overwrite)"):
				m = 'a'
		logfile = open(f,m)

	if args.debug:
		args.verbose = True
	   
	if args.verbose:
		print("Verbose is on")

	global cwd
	cwd = os.getcwd()
	
	supertemp_folder = tempfile.mkdtemp(prefix='temp_',dir=cwd)
	
	dic = open('regex.dic')
	rex = []
	for l in iter(dic):
		if not l.startswith('#'):
			try:
				re.compile(l)
			except:
				log("[!]Warning '%s' is not a valid regular expression" % l)
			else:
				rex.append('^.*({0}).*$'.format(l.strip()))
	rex = '|'.join(rex)
	debug('rex='+rex)
	global regex
	regex = re.compile(rex, re.IGNORECASE|re.MULTILINE)
	dic.close()

	dic = open('blacklist.dic')
	rex = []
	for l in iter(dic):
		if not l.startswith('#'):
			try:
				re.compile(l)
			except:
				log("[!]Warning '%s' is not a valid regular expression" % l)
			else:
				rex.append('^.*(%s).*$' % l.strip())
	rex = '|'.join(rex)
	debug('rex='+rex)
	global blacklist
	blacklist = re.compile(rex, re.IGNORECASE|re.MULTILINE)
	dic.close()

	dic = open('ext.dic')
	global exts
	exts = {}
	for l in iter(dic):
		if not l.startswith('#'):
			e = l.strip().split(',')
			exts[e[0]]=e[1].strip()
	dic.close()

	global archivesext
	archivesext=[	'rar', 
					'zip', 
					'7z', 
					'tar',
					'xlsx',
					'xlsm',
					'docx',
					'docm',
				]

	if args.autorun:
		AnalyseAutorun()
	
	if args.root:
		main()

	cleanExit(0)
		
